package shaders;

import helpers.ColorHelper;

import java.awt.Color;
import javax.vecmath.*;

import essential.Material;
import essential.Perlin;
import essential.Ray;

import primitives.Renderable;

import java.awt.image.BufferedImage;

public abstract class Shader extends Material {

	
	public static enum bumpType {None,Water,Turbulence};
	public static enum MappingType {Triangle,Sphere};
	public String type;
	public BufferedImage textureImage;
	public Perlin per;
	public static double gdiff;
	public static double grefl;
	public static double grefr;
	public Color gradientColor;
	public bumpType bump;
	boolean textureIsSet;
	
	public abstract Shader copy();

	
	protected void copyFromTo(Shader a,Shader b){
		b.color = new Color(a.color.getRGB());
		b.gradientColor = new Color(a.gradientColor.getRGB());
		b.bump = a.bump;
		b.per = a.per;
		b.rIndex = a.rIndex;
		b.refl = a.refl;
		b.refr = a.refr;
		b.diff = a.diff;
		b.spec = a.spec;
		b.textureIsSet = a.textureIsSet;
		b.intensity = a.intensity;
		b.light = a.light;
		b.textureImage = new BufferedImage(a.textureImage.getColorModel(),a.textureImage.copyData(null), false, null);
		if (type != null)
			b.type = new String(a.type);
		
	}
	
	public Shader(){
		this.textureIsSet = false;
		this.type = null;
		this.diff = gdiff;
		this.spec = 1.0 - this.diff;
		this.refl = grefl;
		this.refr = grefr;
		this.color = Color.white;
		this.gradientColor = Color.white;
		this.textureImage = null;
		this.bump = Shader.bumpType.None;
		this.per = new Perlin();
		
	}
	

	protected void createTexture(){
		for(int i = 0; i < textureImage.getWidth(); i++)
			for(int j = 0; j < textureImage.getHeight(); j++){
				Color c = textureColor(i/(double)textureImage.getWidth(),j/(double)textureImage.getHeight());
				Color old = new Color(textureImage.getRGB(i, j));
				textureImage.setRGB(i,j,ColorToInt(ColorHelper.sumColor(c,old)));
			}
	}

	// Function to calculate new color
	public abstract Color textureColor(double x, double y);

	// Function to calculate new normal in bumped shaders
	public abstract double fBump(double x, double y, double z);
	
	// Function to calculate new color
	public abstract Color calculateColor3d(Ray r, Vector3d sp);
	public abstract Color calculateColor2d(int x, int y);

	public boolean hasTexture(){
		return textureImage != null;
	}
	
	public Color getColor(Vector2d uv,MappingType mp){
 		if(textureImage == null)
			return this.color;

 		
 		if( mp == MappingType.Sphere ){
 			int x = (int)(- uv.x*textureImage.getWidth()/2);
 			int y = (int)(-uv.y*textureImage.getHeight()/2);
 	 		return calculateColor2d(x+textureImage.getWidth()/2,y+textureImage.getHeight()/2);

 		}else{
 			int x = (int)(uv.x*textureImage.getWidth());
 			int y = (int)(uv.y*textureImage.getHeight());
 	 		
 	 		if (x == textureImage.getWidth()) x--;
 	 		if (y == textureImage.getHeight()) y--;
 	 		
 			return calculateColor2d(x,y);

 		}
 		

 		

	}

	int ColorToInt(Color c) {
		return (c.getRed() << 16) + (c.getGreen() << 8) + c.getBlue();
	}
	
	
	public Color getColor(Ray r) {	
	
		if(r == null)
			return color;
		
		Renderable  object = r.getLastRenderable();

		// Global coord to Local coord
		Vector3d sp = new Vector3d(r.getIntersectionPoint());
		sp.sub(object.getOrigin());
		
        // Set bump
		if(bump != bumpType.None ){
			Vector3d normal = new Vector3d(r.impactNormal);
			 double f0 = fBump(normal.x      ,normal.y      ,normal.z      ),
			 fx = fBump(normal.x+.0001,normal.y      ,normal.z      ),
			 fy = fBump(normal.x      ,normal.y+.0001,normal.z      ),
			 fz = fBump(normal.x      ,normal.y      ,normal.z+.0001);
			Vector3d normalDiff = new Vector3d((fx - f0) / .0001,(fy - f0) / .0001,(fz - f0) / .0001);
			normal.sub(normalDiff);
			normal.normalize(); 
			r.impactNormal = normal;
		}
		
		
		return calculateColor3d(r,sp);
	}


		
	
}
